package com.hivekion.quartz.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hivekion.common.entity.PagedResultVo;
import com.hivekion.common.exception.BusinessException;
import com.hivekion.quartz.domain.JobEntity;
import com.hivekion.quartz.domain.vo.*;
import com.hivekion.quartz.mapper.JobMapper;
import com.hivekion.quartz.service.IQuartzService;
import org.apache.commons.lang3.StringUtils;
import org.quartz.*;
import org.quartz.impl.matchers.GroupMatcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @authot morning
 * @date 2021/8/4 9:44
 */
@Service
public class QuartzServiceImpl extends ServiceImpl<JobMapper, JobEntity> implements IQuartzService {
    private static Logger log = LoggerFactory.getLogger(QuartzServiceImpl.class);
    @Autowired
    @Qualifier("scheduler")
    private Scheduler scheduler;

    @Override
    public List<JobRunViewVo> queryListOpen() {
        try {
            Set<JobKey> jobKeySet = scheduler.getJobKeys(GroupMatcher.anyGroup());
            List<JobRunViewVo> list = jobKeySet
                    .stream()
                    .map(item -> {
                                JobRunViewVo vo = new JobRunViewVo();
                                try {
                                    vo.setNextFireTime(scheduler.getTriggersOfJob(item).get(0).getNextFireTime());
                                    vo.setPreviousFireTime(scheduler.getTriggersOfJob(item).get(0).getPreviousFireTime());
                                    JobDetail jobDetail = scheduler.getJobDetail(item);
                                    vo.setRunKey(jobDetail.getKey().getName());
                                    vo.setDescription(jobDetail.getDescription());

                                    TriggerKey triggerKey = TriggerKey.triggerKey(item.getName(), item.getGroup());
                                    Trigger.TriggerState state = scheduler.getTriggerState(triggerKey);
                                    vo.setState(state);
                                } catch (SchedulerException e) {
                                    e.printStackTrace();
                                }
                                return vo;
                            }
                    )
                    .collect(Collectors.toList());

            return list;
        } catch (Exception ex) {
            return null;
        }
    }

    public List<JobRunViewVo> queryRuningList() {
        try {
            List<JobExecutionContext> jobContexts = scheduler.getCurrentlyExecutingJobs();
            List<JobRunViewVo> list = jobContexts
                    .stream()
                    .map(
                            item -> {
                                JobRunViewVo vo = new JobRunViewVo();
                                vo.setNextFireTime(item.getNextFireTime());
                                vo.setPreviousFireTime(item.getPreviousFireTime());
                                vo.setRunKey(item.getJobDetail().getKey().getName());
                                vo.setDescription(item.getJobDetail().getDescription());
                                return vo;
                            }
                    )
                    .collect(Collectors.toList());
            return list;
        } catch (Exception ex) {
            return null;
        }
    }

    private static final String JOB_DEFAULT_GROUP_NAME = "job_group_loan_em_";
    private static final String JOB_DEFAULT_NAME = "jobName_";
    private static final String TRIGGER_DEFAULT_NAME = "trigger_";
    private static final String TRIGGER_DEFAULT_GROUP_NAME = "trigger_group_loan_em_";

    @Override
    @Transactional
    public Boolean addJob(JobCreateInputVo job) {
        JobEntity entity = new JobEntity();
        BeanUtils.copyProperties(job, entity);
        this.save(entity);
        String jobId = entity.getId();
        entity.setJobGroup(jobId + "_Group");
        entity.setTriggerName(jobId + "_trigger");             // 触发器名称
        entity.setTriggerGroup(jobId + "_trigger_Group");      //
        this.saveOrUpdate(entity);
        this.startJob(jobId);
        return true;
    }

    @Override
    public void addJob(Class<? extends Job> jobClass, String cronExp, Map<String, Object> data, String jobName) {
        try {
            if (!CronExpression.isValidExpression(cronExp)) {
                log.error("invalid cron expression: {}", cronExp);
                throw new SchedulerException("invalid cron expression");
            }
            JobDetail jobDetail = JobBuilder.newJob(jobClass)
                    .withIdentity(new JobKey(StringUtils.join(jobName), StringUtils.join(jobName)))
                    .build();
            if (data != null && data.size() > 0) {
                jobDetail.getJobDataMap().putAll(data);
            }
            CronTrigger trigger = TriggerBuilder.newTrigger()
                    .forJob(jobDetail)
                    .withIdentity(new TriggerKey(StringUtils.join(jobName), StringUtils.join(jobName)))
                    .withSchedule(CronScheduleBuilder.cronSchedule(cronExp))
                    .build();
            scheduler.scheduleJob(jobDetail, trigger);
            if (!scheduler.isShutdown()) {
                scheduler.start();
            }
        } catch (SchedulerException e) {
            log.error(e.getMessage());
        }
    }

    @Override
    public Boolean updateJob(JobUpdateInputVo inputVo) {
        try {
            JobEntity jobEntity = this.getById(inputVo.getId());
            if (jobEntity == null) {
                throw new BusinessException(500, "未找到定时任务信息！");
            }
            // 判断执行周期和时间是否发生变化，如果发生变化需要更新定时器，如没有发生变化只更新数据库即可
            if (jobEntity.getDateType() == inputVo.getDateType() && inputVo.getSendTime().equals(jobEntity.getSendTime())) {
                BeanUtils.copyProperties(inputVo, jobEntity);
                this.saveOrUpdate(jobEntity);
            } else {
                BeanUtils.copyProperties(inputVo, jobEntity);
                String expression =
                        this.getCronExpression(inputVo.getDateType(), inputVo.getSendTime());
                jobEntity.setCronExpression(expression);
                this.save(jobEntity);
                // 更新scheduler
                TriggerKey triggerKey = new TriggerKey(jobEntity.getTriggerName(), jobEntity.getTriggerGroup());
                CronTrigger cronTrigger = (CronTrigger) scheduler.getTrigger(triggerKey);
                CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(jobEntity.getCronExpression());
                // 重新构件表达式
                CronTrigger trigger = cronTrigger
                        .getTriggerBuilder()
                        .withIdentity(triggerKey)
                        .withSchedule(cronScheduleBuilder)
                        .withDescription(jobEntity.getRemark())
                        .build();
                scheduler.rescheduleJob(triggerKey, trigger);
                log.info("定时任务[{}]更新成功", jobEntity.getJobName());
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    /*
     * 启动定时任务
     * */
    public boolean startJob(String id) {
        JobEntity job = this.getById(id);
        String jobName = job.getJobName();
        String jobUnique = job.getId();
        try {
            JobDetail jobDetail = JobBuilder
                    .newJob((Class<? extends Job>) Class.forName(job.getClassName())) // 指定执行类
                    .withIdentity(jobUnique, job.getJobGroup())                       // 指定name和group
                    .withDescription(jobName + job.getRemark())
                    .requestRecovery()
                    .build();
            // 创建表达式调度构建器
            CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(
                    job.getCronExpression()
            );
            // 创建触发器
            CronTrigger cronTrigger = TriggerBuilder
                    .newTrigger()
                    .withIdentity(job.getTriggerName(), job.getTriggerGroup())
                    .withDescription(job.getRemark())
                    .withSchedule(cronScheduleBuilder)
                    .build();
            scheduler.scheduleJob(jobDetail, cronTrigger);
            scheduler.start();
            log.info("定时任务[{}]创建成功", job.getId() + jobName);
            if (job.getPauseStatus() == 0) {
                scheduler.pauseJob(JobKey.jobKey(jobUnique, job.getJobGroup()));
            }
        } catch (Exception ex) {
            return false;
        }
        return true;
    }

    /*
     * 执行一次
     * */
    @Override
    public Boolean runJob(String id) {
        JobEntity jobEntity = this.getById(id);
        if (jobEntity == null) {
            throw new BusinessException(500, "未找到任务信息");
        }
        this.runJob(jobEntity);
        return false;
    }

    @Override
    public Boolean runJob(JobCreateInputVo job) {
        JobEntity entity = new JobEntity();
        BeanUtils.copyProperties(job, entity);
        this.save(entity);
        String jobId = entity.getId();
        entity.setJobGroup(jobId + "_Group");
        entity.setTriggerName(jobId + "_trigger");             // 触发器名称
        entity.setTriggerGroup(jobId + "_trigger_Group");      //
        this.saveOrUpdate(entity);
        this.runJob(entity);
        return true;
    }

    private boolean runJob(JobEntity entity) {
        String jobId = entity.getId();
        try {
            scheduler.triggerJob(JobKey.jobKey(jobId, entity.getJobGroup()));
            log.info("定时任务[{}]执行成功", jobId);
        } catch (SchedulerException e) {
            e.printStackTrace();
        }
        return true;
    }

    /*
     * 暂停任务
     * */
    @Override
    public Boolean pauseJob(String id) {
        try {
            JobEntity jobEntity = this.getById(id);
            jobEntity.setPauseStatus(0);
            String jobId = jobEntity.getId();
            String jobName = jobEntity.getJobName();
            scheduler.pauseJob(JobKey.jobKey(jobId, jobEntity.getJobGroup()));
            log.info("定时任务[{}]暂停成功", jobName);
            this.saveOrUpdate(jobEntity);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    /*
     * 唤醒Job
     * */
    @Override
    public Boolean resumeJob(String id) {
        try {
            JobEntity jobEntity = this.getById(id);
            jobEntity.setPauseStatus(1);
            String jobId = jobEntity.getId();
            String jobName = jobEntity.getJobName();
            scheduler.resumeJob(JobKey.jobKey(jobId, jobEntity.getJobGroup()));
            log.info("定时任务[{}]唤醒成功", jobName);
            this.saveOrUpdate(jobEntity);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    @Override
    public Boolean deleteJob(String id) {
        try {
            JobEntity jobEntity = this.getById(id);
            if (jobEntity == null) {
                throw new BusinessException(500, "未找到任务信息！");
            }
            scheduler.deleteJob(
                    JobKey.jobKey(jobEntity.getId(), jobEntity.getJobGroup())
            );
            this.removeById(id);
            log.info("定时任务[{}]删除成功", jobEntity.getJobName());
            return true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    @Override
    public void deleteJob2(String jobName) {
        JobKey jobKey = new JobKey(StringUtils.join(jobName), StringUtils.join(jobName));
        try {
            if (!scheduler.checkExists(jobKey)) {
                log.info("job not exists, job name: {}, group name: {}", jobKey.getName(), jobKey.getGroup());
                return;
            }
            TriggerKey triggerKey = TriggerKey.triggerKey(StringUtils.join(jobName), StringUtils.join(jobName));
            scheduler.pauseTrigger(triggerKey);
            scheduler.unscheduleJob(triggerKey);
            scheduler.deleteJob(jobKey);

        } catch (SchedulerException e) {
            log.error(e.getMessage());
        }
    }

    @Override
    public Boolean removes(List<String> ids) {
        ids.forEach(item -> deleteJob(item));
        return true;
    }

    @Override
    public PagedResultVo<JobViewVo> queryList(JobSearchPageVo jobEntity) {
        LambdaQueryWrapper<JobEntity> queryWrapper = Wrappers.lambdaQuery();
        if (StringUtils.isNotBlank(jobEntity.getJobName())) {
            queryWrapper.like(JobEntity::getJobName, jobEntity.getJobName());
        }
        Page<JobEntity> jobPage = new Page<>(
                jobEntity.getPageNum(),
                jobEntity.getPageSize()
        );
        IPage<JobViewVo> jobs = this.page(jobPage, queryWrapper)
                .convert(
                        item -> {
                            JobViewVo vo = new JobViewVo();
                            BeanUtils.copyProperties(item, vo);
                            return vo;
                        }
                );
        Integer total = (int) jobs.getTotal();
        PagedResultVo<JobViewVo> resultVo = new PagedResultVo<JobViewVo>(
                jobEntity,
                total,
                jobs.getRecords()
        );
        return resultVo;
    }

    @Override
    public JobModelVo getInfo(String id) {
        JobEntity entity = this.getById(id);
        if (entity == null) {
            throw new BusinessException(500, "未找到定时任务");
        }
        JobModelVo vo = new JobModelVo();
        BeanUtils.copyProperties(entity, vo);
        return vo;
    }

    private String getCronExpression(int dateType, Date sendTime) {
        String ss = sendTime.getSeconds() + "";
        String mm = sendTime.getMinutes() + "";
        String hh = sendTime.getHours() + "";

        String expression = "";
        switch (dateType) {
            case 0: // 天
                expression = ss + " " + mm + " " + hh + " * * ?";
                break;
            case 1: // 周
                expression = ss + " " + mm + " " + hh + " ? 0 1 *";
                break;
            case 2: // 月
                expression = ss + " " + mm + " " + hh + " ? 1 ? *";
                break;
            case 3: // 季度
                expression = ss + " " + mm + " " + hh + " ? 1/3 ? *";
                break;
            case 4: // 年
                expression = ss + " " + mm + " " + hh + " ? ? *";
                break;
            case 5: // 秒
                expression = ss + " * * * * ? ";
                break;
            case 6: // 分钟
                expression = ss + " " + mm + " * * * ? ";
                break;
            case 7: // 小时
                expression = ss + " " + mm + " " + hh + " * * ?";
                break;
        }
        return expression;
    }
}
